﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Xml;

namespace AppDesigner.XamlDom
{
    public class XamlProperty
    {
        #region Fields

        private XamlObject _parentObject;
        private XamlPropertyInfo _propertyInfo;
        private XamlPropertyValue _propertyValue;
        private CollectionElementsCollection collectionElements;
        private bool isCollection;

        private static readonly IList<XamlPropertyValue> emptyCollectionElementsArray = new XamlPropertyValue[0];


        #endregion

        #region Constrcutor

        public XamlProperty(XamlObject parentObj, XamlPropertyInfo propertyInfo, XamlPropertyValue propertyValue)
            : this(parentObj,propertyInfo)
        {
            PossiblyNameChanged(null, propertyValue);
            this._propertyValue = propertyValue;

            if (propertyValue != null)
            {
                propertyValue.ParentProperty = this;
            }
            UpdateValueOnInstance();
        }

        public XamlProperty(XamlObject parentObject, XamlPropertyInfo propertyInfo)
        {
            this._parentObject = parentObject;
            this._propertyInfo = propertyInfo;

            if (propertyInfo.IsCollection)
            {
                isCollection = true;
                collectionElements = new CollectionElementsCollection(this);
            }
        }

        #endregion

        #region Property

        public XamlPropertyInfo PropertyInfo
        {
            get { return _propertyInfo; }
            set { _propertyInfo = value; }
        }

        public XamlObject ParentObject
        {
            get
            {
                return _parentObject;
            }
        }

        public string PropertyName
        {
            get
            {
                return _propertyInfo.Name;
            }
        }

        public Type PropertyTargetType
        {
            get
            {
                return _propertyInfo.TargetType;
            }
        }
        
        public bool IsAdvanced
        {
            get
            {
                return _propertyInfo.IsAdvanced;
            }
        }

        public DependencyProperty DependencyProperty
        {
            get
            {
                return _propertyInfo.DependencyProperty;
            }
        }

        public bool IsAttached
        {
            get
            {
                return _propertyInfo.IsAttached;
            }
        }

        public bool IsEvent
        {
            get
            {
                return _propertyInfo.IsEvent;
            }
        }

        public Type ReturnType
        {
            get
            {
                return _propertyInfo.ReturnType;
            }
        }

        public TypeConverter TypeConverter
        {
            get
            {
                return _propertyInfo.TypeConverter;
            }
        }

        public string Category
        {
            get
            {
                return _propertyInfo.Category;
            }
        }

        public XamlPropertyValue PropertyValue
        {
            get { return _propertyValue; }
            set { SetPropertyValue(value); }
        }

        public bool IsCollection
        {
            get
            {
                return isCollection;
            }
        }

        public IList<XamlPropertyValue> CollectoinElements
        {
            get { return collectionElements ?? emptyCollectionElementsArray; }
        }

        public bool IsSet
        {
            get
            {
                return _propertyValue != null || _propertyElement != null;
            }
        }

        public event EventHandler IsSetChanged;
        public event EventHandler ValueChanged;
        public event EventHandler ValueOnInstanceChanged;

        

        #endregion

        #region Method

        private void SetPropertyValue(XamlPropertyValue value)
        {
            bool wasSet = this.IsSet;
            PossiblyNameChanged(_propertyValue, value);
            //reset expression
            var xamlObject = _propertyValue as XamlObject;
            if (xamlObject != null && xamlObject.IsMarkupExtension)
            {
                _propertyInfo.ResetValue(_parentObject.Instance);
            }
            ResetInternal();
            _propertyValue = value;
            _propertyValue.ParentProperty = this;
            _propertyValue.AddNodeTo(this);
            UpdateValueOnInstance();
            ParentObject.OnPropertyChanged(this);

            if (!wasSet)
            {
                if (IsSetChanged != null)
                {
                    IsSetChanged(this, EventArgs.Empty);
                }
                if (ValueChanged != null)
                {
                    ValueChanged(this, EventArgs.Empty);
                }
            }
        }

        private void ResetInternal()
        {
            if (_propertyValue != null)
            {
                _propertyValue.RemoveNodeFromParent();
                _propertyValue.ParentProperty = null;
                _propertyValue = null;
            }
            if (_propertyElement != null)
            {
                _propertyElement.ParentNode.RemoveChild(_propertyElement);
                _propertyElement = null;
            }
        }

        public object ValueOnInstance
        {
            get
            {
                if (IsEvent)
                {
                    if (_propertyValue != null)
                    {
                        return _propertyValue.GetValueFor(null);
                    }
                    else
                    {
                        return null;
                    }
                }
                else
                {
                    return _propertyInfo.GetValue(_parentObject.Instance);
                }
            }
            set
            {
                _propertyInfo.SetValue(_parentObject.Instance, value);
                if (ValueOnInstanceChanged != null)
                {
                    ValueOnInstanceChanged(this, EventArgs.Empty);
                }
            }
        }

        public void UpdateValueOnInstance()
        {
            if (PropertyValue != null)
            {
                try
                {
                    ValueOnInstance = PropertyValue.GetValueFor(_propertyInfo);
                }
                catch
                {
                    Debug.WriteLine("UdpateValueOnInsatance falied.");
                }
            }
        }

        private void PossiblyNameChanged(XamlPropertyValue oldValue, XamlPropertyValue newValue)
        {
            if (PropertyName == "Name" && ReturnType == typeof(string))
            {
                string oldName = null;
                string newName = null;

                var oldTextValue = oldValue as XamlTextValue;
                if (oldTextValue != null)
                {
                    oldName = oldTextValue.Text;
                }
                var newTextValue = newValue as XamlTextValue;
                if (newTextValue != null)
                {
                    newName = newTextValue.Text;
                }

                var obj = ParentObject;
                while (obj != null)
                {
                    var namescope = obj.Instance as INameScope;
                    if (namescope == null)
                    {
                        if (obj.Instance is DependencyObject)
                        {
                            namescope = NameScope.GetNameScope((DependencyObject)obj.Instance);
                        }
                    }
                    else
                    {
                        if (oldName != null)
                        {
                            try
                            {
                                namescope.UnregisterName(oldName);
                            }
                            catch (Exception ex)
                            {
                                Debug.WriteLine(ex.Message.ToString());
                            }
                        }
                        if (newName != null)
                        {
                            namescope.RegisterName(newName, ParentObject.Instance);
                        }
                        break;
                    }
                    obj = obj.ParentObject;
                }
            }
        }

        XmlElement _propertyElement;

        /// <summary>
        /// add a collection element that already is part of the xml dom.
        /// </summary>
        /// <param name="collectionPropertyElement"></param>
        /// <param name="val"></param>
        public void ParserAddCollectionElement(XmlElement collectionPropertyElement, XamlPropertyValue val)
        {
            if (collectionPropertyElement != null && _propertyElement == null)
            {
                ParserSetPropertyElement(collectionPropertyElement);
            }
            collectionElements.AddInternal(val);
            val.ParentProperty = this;
            if (collectionPropertyElement != _propertyElement)
            {
                val.RemoveNodeFromParent();
                val.AddNodeTo(this);
            }
        }

        public void ParserSetPropertyElement(XmlElement propertyElement)
        {
            XmlElement oldPropertyElement = _propertyElement;
            if (oldPropertyElement == propertyElement) return;

            _propertyElement = propertyElement;
            if (oldPropertyElement != null && isCollection)
            {
                Debug.WriteLine("Property element for " + this.PropertyName + " already exists, mergin....");
                foreach (XamlPropertyValue val in this.collectionElements) 
                {
                    val.RemoveNodeFromParent();
                    val.AddNodeTo(this);
                }
                oldPropertyElement.ParentNode.RemoveChild(oldPropertyElement);
            }
        }

        public XmlAttribute SetAttribute(string value)
        {
            string ns = ParentObject.OwnerDocument.GetNamespaceFor(PropertyTargetType);
            string name;
            if (IsAttached)
            {
                name = PropertyTargetType.Name + "." + PropertyName;
            }
            else
            {
                name = PropertyName;
            }
            XmlElement element = ParentObject.XmlElement;
            if (string.IsNullOrEmpty(element.GetPrefixOfNamespace(ns)))
            {
                element.SetAttribute(name, value);
                return element.GetAttributeNode(name);
            }
            else
            {
                element.SetAttribute(name, ns, value);
                return element.GetAttributeNode(name, ns);
            }
        }

        /// <summary>
        /// get extension for the markup
        /// </summary>
        /// <returns></returns>
        public string GetNameForMarkupExtension()
        {
            string name;
            if (IsAttached)
            {
                name = PropertyTargetType.Name + "." + PropertyName;
            }
            else
            {
                name = PropertyName;
            }
            var element = ParentObject.XmlElement;
            string ns = ParentObject.OwnerDocument.GetNamespaceFor(PropertyTargetType);
            var prefix = element.GetPrefixOfNamespace(ns);
            if (string.IsNullOrEmpty(prefix))
            {
                return name;
            }
            else
            {
                return prefix + ":" + name;
            }
        }

        public void AddChildNodeToProperty(XmlNode newChildNode)
        {
            if (this.IsCollection)
            {
                InsertNodeInCollection(newChildNode, collectionElements.Count);
                return;
            }
            if (_propertyElement == null)
            {
                if (PropertyName == _parentObject.ContentPropertyName)
                {
                    _parentObject.XmlElement.InsertBefore(newChildNode, _parentObject.XmlElement.FirstChild);
                    return;
                }
                _propertyElement = _parentObject.OwnerDocument.XmlDocument.CreateElement(this.PropertyTargetType.Name + "." + this.PropertyName, _parentObject.OwnerDocument.GetNamespaceFor(this.PropertyTargetType));
                _parentObject.XmlElement.InsertBefore(_propertyElement, _parentObject.XmlElement.FirstChild);
            }
            _propertyElement.AppendChild(newChildNode);
        }

        internal void AddChildNodeToProperty(System.Xml.XmlText _textNode)
        {
            throw new NotImplementedException();
        }

        public void InsertNodeInCollection(XmlNode newChildNode, int index)
        {
            if (index >= 0 && index <= collectionElements.Count)
            {
                throw new ArgumentException("index is invalid.");
            }

            XmlElement collection = _propertyElement;
            if (collection == null)
            {
                if (collectionElements.Count == 0 && this.PropertyName != this.ParentObject.ContentPropertyName)
                {
                    _propertyElement = _parentObject.OwnerDocument.XmlDocument.CreateElement(this.PropertyTargetType.Name + "." + this.PropertyName, _parentObject.OwnerDocument.GetNamespaceFor(this.PropertyTargetType));
                    _parentObject.XmlElement.AppendChild(_propertyElement);
                    collection = _propertyElement;
                }
                else
                {
                    //default collection;
                    collection = _parentObject.XmlElement;
                }
            }
            if (collectionElements.Count == 0)
            {
                collection.AppendChild(newChildNode);
            }
            else if (index == collectionElements.Count)
            {
                collection.InsertAfter(newChildNode, collectionElements[collectionElements.Count - 1].GetNodeForCollection());
            }
            else
            {
                collection.InsertBefore(newChildNode, collectionElements[index].GetNodeForCollection());
            }
        }

        #endregion
        
    }
}
